#########################################################
#                                                       #
# THIS SOFTWARE IS (C) J.Y.Amihud and contributors 2022 #
# AND IT'S UNDER THE GNU AGPLv3 or any later version.   #
#                                                       #
#########################################################

# I will try to do something crazy stupid. I will try to
# Make an entire graphical editor using GTK in one python
# script. Which will not be very wise. But I will give it
# a shot.

# A lot of the decisions will be made because of this fact
# so be aware of that.

#########################################################
#                                                       #
#               IMPORTING VARIOUS MODULES               #
#                                                       #
#########################################################

import os
import time
import json
import threading
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GLib


#########################################################
#                                                       #
#                 CONFIGURING THE WINDOW                #
#                                                       #
#########################################################

win = Gtk.Window()
win.set_size_request(600, 600)  
win.connect("destroy", Gtk.main_quit)
scrl = Gtk.ScrolledWindow()
win.add(scrl)
box = Gtk.VBox()
scrl.add(box)

#########################################################
#                                                       #
#                  LOADING PRESETS                      #
#                                                       #
#########################################################

# I want to load all kinds of presets for the auto-fill
# For this we will nee to read all of the files. And it
# will be done before we have a GUI running. So the out
# put of this should be done in terminal it self.

def pbar(now, total):

    # Simple terminal progress bar

    ################------------------------------

    # That's roughly 30% with such a graph.
    
    w, h = os.get_terminal_size()

    n = "#"
    t = "-"

    ns = int(w/total*now)

    string = "\r"+(n*ns)+(t*(w-ns))
    print(string, end="")



# We assume that it's in a correct folder
print("   ----- LOADING AUTO-FILL DATA ------  ")

win.full = {}

all_apps = list(os.listdir(os.getcwd()+"/apps"))
for n, i in enumerate(all_apps):
    pbar(n, len(all_apps))
    if i.endswith(".json"):
        # we found a file on an app:
        try:
            with open("apps/"+i) as f:
                this_file = json.load(f)
            for key in this_file:
                if key not in win.full:
                    win.full[key] = this_file[key]
                elif type(this_file[key]) == list:
                    for item in this_file[key]:
                        if item not in win.full[key]:
                            win.full[key].append(item)
                elif type(this_file[key]) == dict:
                    for thekey in this_file[key]:
                        win.full[key][thekey] = ""
                            
                    
        except Exception as e:
            pass

            
print("   ----- DONE LOADING AUTO-FILL DATA ------  ")

#########################################################
#                                                       #
#               LIST EDITOR ( TAG EDITOR )              #
#                                                       #
#########################################################

# This piece of code I wrote for FastLBRY GTK, but it will
# be very handy here. I did a few modifications to remove
# the dependancy on the icon system in FastLBRY GTK

def tags_editor(win, data, return_edit_functions=False, auto_fill=[]):

    def update(new_data):
        old = data.copy()
        for t in old:
            data.remove(t)
        for t in new_data:
            data.append(t)
        for i in tagsbox.get_children():
            i.destroy()
        for tag in data:
            add_tag(tag)
        
    
    tagscont = Gtk.HBox()
    
    
    tagscrl = Gtk.ScrolledWindow()
    tagscrl.set_size_request(40,40)
    tagscont.pack_start(tagscrl, True, True, 0)

    tagsbox = Gtk.HBox()
    tagscrl.add_with_viewport(tagsbox)

    def add_tag(tag):

        if not tag:
            return
        
        if tag not in data:
            data.append(tag)
        tagb = Gtk.HBox()
        tagb.pack_start(Gtk.Label("  "+tag+"  "), False, False, 0)
        def kill(w):
            tagb.destroy()
            data.remove(tag)
        tagk = Gtk.Button("-")
        tagk.connect("clicked", kill)
        tagk.set_relief(Gtk.ReliefStyle.NONE)
        tagb.pack_start(tagk, False, False, 0)
        tagb.pack_start(Gtk.VSeparator(), False, False, 5)
        tagsbox.pack_start(tagb, False, False, 0)
        tagsbox.show_all()

        # Scroll to the last
        def later():
            time.sleep(0.1)
            def now():
                a = tagscrl.get_hadjustment()
                a.set_value(a.get_upper())
            GLib.idle_add(now)
        load_thread = threading.Thread(target=later)
        load_thread.start()
        # The threading is needed, since we want to wait
        # while GTK will update the UI and only then move
        # the adjustent. Becuase else, it will move to the
        # last previous, not to the last last.
            
    addt = Gtk.Button("+")
    addt.set_relief(Gtk.ReliefStyle.NONE)
    tagscont.pack_end(addt, False, False, 0)
    def on_entry(w):
        add_tag(tagentry.get_text())
        tagentry.set_text("")



    tagentry = Gtk.Entry()

    if auto_fill:
    
        liststore = Gtk.ListStore(str)
        completion = Gtk.EntryCompletion()
        completion.set_model(liststore)
        completion.set_text_column(0)

        for i in auto_fill:
            liststore.append((i,))

        tagentry.set_completion(completion)
        completion.set_minimum_key_length(0)
        completion.complete()
    
    tagentry.connect("activate", on_entry)
    addt.connect("clicked", on_entry)
    tagscont.pack_end(tagentry, False, False, False)

    
    
    for tag in data:
        add_tag(tag)

    if not return_edit_functions:
        return tagscont
    else:
        return tagscont, update



#########################################################
#                                                       #
#                       TOOL BAR                        #
#                                                       #
#########################################################

# Tool bar will not going to have icons and things like
# this. It's not about being pretty. It's about giving
# the contributor a handy tool to contribute.


pannel = Gtk.HeaderBar()
pannel.set_show_close_button(True)
win.set_titlebar(pannel)


# We are going to make a little trick on our self ( I do
# that everywhere ). I gonna put important variables into
# the GTK Window.

win.data_is = {
    "names":[],
    "comment":"",
    "links":{},
    "licenses":[],
    "platforms":[],
    "interface":[],
    "languages":[],
    "networks_read":[],
    "networks_write":[],
    "formats_read":[],
    "formats_write":[],
    "generic_name":[],
    "issues":[],
    
}

##################### NEW  BUTTON #######################

def type_min(i):
    if type(win.data_is[i]) == list:
        return []
    elif type(win.data_is[i]) == dict:
        return {}
    elif type(win.data_is[i]) == str:
        return ""

def on_new(w):

    
    for i in win.data_is:
        
        try:
            updaters[i](type_min(i))
        except Exception as e:
            win.data_is[i] = type_min(i)

new_button = Gtk.Button("New")
new_button.set_relief(Gtk.ReliefStyle.NONE)
new_button.connect("clicked", on_new)
pannel.pack_start(new_button)

##################### OPEN BUTTON #######################

def on_open(w):

    dialog = Gtk.FileChooserDialog("Choose a file",
                                   None,
                                   Gtk.FileChooserAction.OPEN,
                                   (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                                    Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
    dialog.set_current_folder(os.getcwd()+"/apps")

    filter_sup = Gtk.FileFilter()
    filter_sup.set_name("Json Files")
    filter_sup.add_pattern("*.json")
    dialog.add_filter(filter_sup)
    
    response = dialog.run()
    if response == Gtk.ResponseType.OK:

        # LOADING THE FILE
        
        with open(dialog.get_filename()) as f:
            loaded_file = json.load(f)

        
            
        for i in win.data_is:
            if i in loaded_file  and type(win.data_is[i]) == type(loaded_file[i]):
                
                try:
                    updaters[i](loaded_file.get(i, type_min(i)))
                except Exception as e:
                    win.data_is[i] = loaded_file.get(i, type_min(i))
            else:
                try:
                    updaters[i](loaded_file.get(i, type_min(i)))
                except:
                    win.data_is[i] = loaded_file.get(i, type_min(i))

        
        
        
                    
    dialog.destroy()

open_button = Gtk.Button("Open")
open_button.set_relief(Gtk.ReliefStyle.NONE)
open_button.connect("clicked", on_open)
pannel.pack_start(open_button)

##################### SAVE BUTTON #######################

# save_button = Gtk.Button("Save")
# pannel.pack_start(save_button)

#################### SAVE AS BUTTON #####################

def on_save_as(w):

    tb = detext.get_buffer()
    win.data_is["comment"] = tb.get_text(tb.get_start_iter(), tb.get_end_iter(), True)
    
    dialog = Gtk.FileChooserDialog("Choose a file",
                                   None,
                                   Gtk.FileChooserAction.SAVE,
                                   (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                                    Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
    dialog.set_current_folder(os.getcwd()+"/apps")

    filter_sup = Gtk.FileFilter()
    filter_sup.set_name("Json Files")
    filter_sup.add_pattern("*.json")
    dialog.add_filter(filter_sup)
    
    response = dialog.run()
    if response == Gtk.ResponseType.OK:
        savename = dialog.get_filename()
        if not savename.endswith(".json"):
            savename = savename+".json"

        with open(savename, 'w') as f:
                json.dump(win.data_is, f, indent=4, sort_keys=True)
    
    dialog.destroy()
    
save_as_button = Gtk.Button("Save As")
save_as_button.connect("clicked", on_save_as)
save_as_button.set_relief(Gtk.ReliefStyle.NONE)
pannel.pack_start(save_as_button)

#########################################################
#                                                       #
#                      EDITOR IT SELF                   #
#                                                       #
#########################################################

updaters = {} # Very important to make tags editor work

#################### NAMES / COMMENT ####################

collapsable = Gtk.Expander(label="  Names / Comment:  ")
box.pack_start(collapsable, 0,0,5)
term_box = Gtk.VBox()
collapsable.add(term_box)

# The list of names
term_box.pack_start(Gtk.Label("List of Names"),0,0,5)
names_editor, updaters["names"] = tags_editor(win, win.data_is["names"], True)
term_box.pack_start(names_editor,0,0,5)

# The list of names
term_box.pack_start(Gtk.Label("Generic Names ( Features )"),0,0,5)
generic_editor, updaters["generic_name"] = tags_editor(win, win.data_is["generic_name"], True, win.full.get("generic_name",[]))
term_box.pack_start(generic_editor,0,0,5)

term_box.pack_start(Gtk.Label("Comment (HTML)"),0,0,5)

def comment_updater(new):
    detext.get_buffer().set_text(new)
    win.data_is["comment"] = new

updaters["comment"] = comment_updater

descrl = Gtk.ScrolledWindow()
descrl.set_size_request(100,100)
detext = Gtk.TextView()
detext.set_wrap_mode(Gtk.WrapMode.WORD)
detext.get_buffer().set_text(win.data_is["comment"])
descrl.add(detext)

term_box.pack_start(descrl,0,0,5)

####################     LINKS       ####################

collapsable = Gtk.Expander(label="  Links:  ")
box.pack_start(collapsable, 0,0,5)
term_box = Gtk.VBox()
collapsable.add(term_box)

# Links are a bit more complicated. It will require a
# special editor.

def add_item_to_view(category, link):
    itembox = Gtk.HBox()
    itembox.pack_start(Gtk.Label("  "+category+":  "), 0,0,0)

    itembox.pack_start(Gtk.VSeparator(), 0,0,0)

    itembox.pack_start(Gtk.Label("  "+link+"  "), 0,0,0)

    def on_destroy(w, itembox, category):

        itembox.destroy()
        del win.data_is["links"][category]
        

    destroy = Gtk.Button("-")
    destroy.set_relief(Gtk.ReliefStyle.NONE)
    destroy.connect("clicked", on_destroy, itembox, category)
    itembox.pack_end(destroy, 0,0,0)
        
    linksbox.pack_end(itembox, 0,0,0)
    linksbox.show_all()
    
def add_item(w):

    key =  categoryInput.get_text()
    value = linkInput.get_text()
    if key not in win.data_is["links"]:
        add_item_to_view(key, value)
        win.data_is["links"][key] = value

        categoryInput.set_text("")
        linkInput.set_text("")
        categoryInput.grab_focus()

def links_updater(new):

    for i in linksbox.get_children():
        i.destroy()

    win.data_is["links"] = {}
        
    for i in new:
        add_item_to_view(i, new[i])
        win.data_is["links"][i] = new[i]
updaters["links"] = links_updater
        
inputbox = Gtk.HBox()
term_box.pack_start(inputbox, 1, 0, 5)

inputbox.pack_start(Gtk.Label("Category:"), 0,0,1)
categoryInput = Gtk.Entry()
inputbox.pack_start(categoryInput, 0,0,1)

liststore = Gtk.ListStore(str)
completion = Gtk.EntryCompletion()
completion.set_model(liststore)
completion.set_text_column(0)

for i in list(win.full["links"].keys()):
    liststore.append((i,))

categoryInput.set_completion(completion)
completion.set_minimum_key_length(0)

completion.complete()


inputbox.pack_start(Gtk.Label("Link:"), 0,0,1)
linkInput = Gtk.Entry()
linkInput.connect("activate", add_item)
inputbox.pack_start(linkInput, 1,1,1)

addInput = Gtk.Button("+")
addInput.connect("clicked", add_item)
addInput.set_relief(Gtk.ReliefStyle.NONE)
inputbox.pack_end(addInput, 0,0,1)

linksbox = Gtk.VBox()
term_box.pack_start(Gtk.HSeparator(), 1, 0, 5)
term_box.pack_start(linksbox, 1, 0, 5)




####################   LICENSES      ####################

collapsable = Gtk.Expander(label="  Licenses:  ")
box.pack_start(collapsable, 0,0,5)
term_box = Gtk.VBox()
collapsable.add(term_box)

# The list of licenses
#term_box.pack_start(Gtk.Label("Licenses:"),0,0,5)
licenses_editor, updaters["licenses"] = tags_editor(win, win.data_is["licenses"], True, win.full.get("licenses",[]))
term_box.pack_start(licenses_editor,0,0,5)

####################   NERDY INFO      ##################

collapsable = Gtk.Expander(label="  Technical Info:  ")
box.pack_start(collapsable, 0,0,5)
term_box = Gtk.VBox()
collapsable.add(term_box)

# The list of Platforms
term_box.pack_start(Gtk.Label("Platforms:"),0,0,5)
platforms_editor, updaters["platforms"] = tags_editor(win, win.data_is["platforms"], True, win.full.get("platforms",[]))
term_box.pack_start(platforms_editor,0,0,5)


# The list of Interfaces
term_box.pack_start(Gtk.Label("Interfaces:"),0,0,5)
interface_editor, updaters["interface"] = tags_editor(win, win.data_is["interface"], True, win.full.get("interface",[]))
term_box.pack_start(interface_editor,0,0,5)


# The list of Languages
term_box.pack_start(Gtk.Label("Languages:"),0,0,5)
languages_editor, updaters["languages"] = tags_editor(win, win.data_is["languages"], True, win.full.get("languages",[]))
term_box.pack_start(languages_editor,0,0,5)

#################### FORMATS / NETWORKS #################

collapsable = Gtk.Expander(label="  Formats / Networks:  ")
box.pack_start(collapsable, 0,0,5)
term_box = Gtk.VBox()
collapsable.add(term_box)

# The list of Networs read
term_box.pack_start(Gtk.Label("Networks it can access ( read ):"),0,0,5)
networks_read_editor, updaters["networks_read"] = tags_editor(win, win.data_is["networks_read"], True, win.full.get("networks_read",[]))
term_box.pack_start(networks_read_editor,0,0,5)


# The list of Networs write
term_box.pack_start(Gtk.Label("Networks it can post to ( write ):"),0,0,5)
networks_write_editor, updaters["networks_write"] = tags_editor(win, win.data_is["networks_write"], True, win.full.get("networks_write",[]))
term_box.pack_start(networks_write_editor,0,0,5)


# The list of Opens files
term_box.pack_start(Gtk.Label("File formats it reads:"),0,0,5)
formats_read_editor, updaters["formats_read"] = tags_editor(win, win.data_is["formats_read"], True, win.full.get("formats_read",[]))
term_box.pack_start(formats_read_editor,0,0,5)


# The list of saves files
term_box.pack_start(Gtk.Label("File formats it saves to:"),0,0,5)
formats_write_editor, updaters["formats_write"] = tags_editor(win, win.data_is["formats_write"], True, win.full.get("formats_write",[]))
term_box.pack_start(formats_write_editor,0,0,5)

#################### ANTI-FEATURES #################

collapsable = Gtk.Expander(label="  Anti-Features ( Malware ):  ")
box.pack_start(collapsable, 0,0,5)
term_box = Gtk.VBox()
collapsable.add(term_box)

# The list of saves files
issues_editor, updaters["issues"] = tags_editor(win, win.data_is["issues"], True, win.full.get("issues",[]))
term_box.pack_start(issues_editor,0,0,5)



#########################################################
#                                                       #
#                   STARTING EVERYTHING                 #
#                                                       #
#########################################################

win.show_all()
Gtk.main()
